旧游无处不堪寻
无寻处,惟有少年心
C Primer Plus(三)

本篇主要介绍一下输入、输出以及缓冲输入和无缓冲输入相关知识。

在计算机编程过程中,输入和输出是绕不开的知识点,如输入输出设备、数据的输入输出、以及输入输出函数(I/O 函数)等等。I/O 函数(如 printf()、scanf()、getchar()、putchar() 等)负责把信息传送到程序中。要注意的是输入/输出函数并不是 C 定义的一部分,C 把开发这些函数的任务留给编译器的实现者来完成。在实际应用中,UNIX 系统中的 C 实现为这些函数提供了一个模型。ANSI C 库则吸取成功的经验,把大量 UNIX I/O函数囊括其中。

单字符 I/O 函数


getchar() 和 putchar() 每次只处理一个字符。我们称之为单字符 I/O 函数。getchar() 和 putchar() 函数包含在 stdio.h 头文件中(其实,getchar() 和 putchar() 都不是真正的函数,它们被定义为供预处理器使用的宏,之后我们会再讨论),我们看一个简单的例子:

#include <stdio.h>

int main(void) {
char ch;
while((ch = getchar()) != '#') {
putchar(ch);
}
return 0;
}

为何输入的字符能直接显示在屏幕上?如果用一个特殊字符(如上面的例子 #)来结束输入,就无法在文本中使用这个字符,是否有更好的方法结束输入?

要回答这些问题,我们首先要了解 C 程序如何处理键盘输入,尤其是缓冲和标准输入文件的概念。

缓冲区


在老式系统,如果用户输入字符后立即重复打印该字符属于无缓冲输入。对于现代大部分系统在用户按下 Enter 键之前不会重复打印刚输入的字符,这种输入形式属于缓冲输入。用户输入的字符被收集并储存在一个被称为缓冲区(buffer)的临时存储区,按下Enter键后,程序才可使用用户输入的字符。ANSI C 和后续的 C 标准都规定输入是缓冲的。

缓冲分为两类:完全缓冲 I/O 和行缓冲 I/O。

  • 完全缓冲输入指的是当缓冲区被填满时才刷新缓冲区(内容被发送至目的地),通常出现在文件输入中。缓冲区的大小取决于系统,常见的大小是 512 字节和 4096字节
  • 行缓冲 I/O 指的是在出现换行符时刷新缓冲区。键盘输入通常是行缓冲输入,所以在按下 Enter 键后才刷新缓冲区

文件、流和键盘输入


C 是一门强大、灵活的语言,有许多用于打开、读取、写入和关闭文件的库函数。从较低层面上,C 可以使用主机操作系统的基本文件工具直接处理文件,这些直接调用操作系统的函数被称为底层 I/O (low-level I/O)。由于计算机系统各不相同,所以不可能为普通的底层 I/O 函数创建标准库,ANSI C 也不打算这样做。而从较高层面上,C 可以通过标准 I/O 包(standard I/O package)来处理文件。在这一层面上,具体的 C 实现负责处理不同系统的差异。

使用标准 I/O 包,无需考虑例如不同的系统储存文件的方式、使用什么换行符标记行末尾等差异。从概念上看,C 程序处理的是流而不是直接处理文件。流(stream)是一个实际输入或输出映射的理想化数据流。打开文件的过程就是把流与文件相关联,而且读写都通过流来完成。
我们要把键盘和显示设备视为每个 C 程序自动打开的文件。stdin 流表示键盘输入,stdout 流表示屏幕输出。getchar()、putchar()、printf() 和 scanf() 函数都是标准 I/O 包的成员,处理这两个流。

文件结尾

检测文件结尾的一种方法是,在文件末尾放一个特殊的字符标记文件结尾。IBM-DOS 和 MS-DOS 的文本文件曾经用过这种方法。这些操作系统可以使用内嵌的 Ctrl+Z 字符来标记文件结尾。

操作系统使用的另一种方法是储存文件大小的信息。MS-DOS 及其相关系统使用这种方法处理二进制文件,因为用这种方法可以在文件中储存所有的字符,包括 Ctrl+Z。新版的 DOS 也使用这种方法处理文本文件。UNIX 使用这种方法处理所有的文件。

无论操作系统实际使用何种方法检测文件结尾,在 C 语言中,用 getchar() 读取文件检测到文件结尾时将返回一个特殊的值,即 EOF(end of file)。scanf() 函数检测到文件结尾时也返回 EOF。通常, EOF 定义在 stdio.h 文件中:

#define EOF (-1)

因为 getchar() 函数的返回值通常都介于 0 - 127,这些值对应标准字符集。但是,如果系统能识别扩展字符集,该函数的返回值可能在 0 - 255 之间。无论哪种情况,-1 都不对应任何字符,所以,该值可用于标记文件结尾。

注意: 如果使用键盘输入,要设法输入 EOF 字符。在大多数 UNIX 和 Linux 系统中,在一行开始处按下 Ctrl+D 会传输文件结尾信号。许多微型计算机系统都把一行开始处的 Ctrl+Z 识别为文件结尾信号,一些系统把任意位置的 Ctrl+Z 解释成文件结尾信号。

注意: scanf() 返回值是到第一个错误的输入形式为止,所有符合格式符的正确输入的个数。